/*******************************************************************************
 * Copyright (c) 2010 European Software Institute - Tecnalia.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Author - Adri�n Noguero (adrian.noguero@esi.es)
 *     
 *******************************************************************************/
package es.esi.gemde.modeltransformator.ui.wizards;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import es.esi.gemde.core.CorePlugin;
import es.esi.gemde.core.resources.OverlaidImageDescriptor;
import es.esi.gemde.core.resources.IGemdeResource.ResourceType;
import es.esi.gemde.modeltransformator.ModelTransformatorPlugin;
import es.esi.gemde.modeltransformator.exceptions.NoSuchEngineException;
import es.esi.gemde.modeltransformator.exceptions.TransformationEngineException;
import es.esi.gemde.modeltransformator.service.ITransformation;

/**
 * A wizard that gathers information from the user to run a registered transformation on a model.
 *
 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
 * @version 1.0.4
 * @since 1.0
 *
 */
public class ExecuteTransformationWizard extends Wizard {

	private ITransformation selectedTransformation;
	private File outputPath;
	private List<EObject> inputs;
	private IWizardPage [] pages;
	private boolean inputPageUsed;
	private HashMap<String, IWizardPage> map; 
	private boolean canFinish;
	private IStatus results;
	private boolean errors = false;
	private Exception caughtException;
	
	/**
	 * Default constructor of the Wizard.
	 */
	public ExecuteTransformationWizard() {
		super();
		setWindowTitle("Execute transformation");
		selectedTransformation = null;
		outputPath = null;
		inputs = new Vector<EObject>();
		pages = new IWizardPage [2];
		inputPageUsed = false;
		canFinish = false;
		map = new HashMap<String, IWizardPage>();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#performFinish()
	 */
	@Override
	public boolean performFinish() {
		errors = false;
		
		try {
			ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell());
			dialog.run(true, false, new IRunnableWithProgress(){ 
				
				@Override
				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
					try {
						monitor.beginTask("Executing the transformation...", IProgressMonitor.UNKNOWN);
						results = ModelTransformatorPlugin.getService().executeTransformation(inputs, selectedTransformation.getName(), selectedTransformation.getEngine(), outputPath.getAbsolutePath());
					}
					catch (IOException e) {
						// Should never occur!!
						caughtException = e;
						errors = true;
					} catch (IllegalArgumentException e) {
						// Should never occur!!
						caughtException = e;
						e.printStackTrace();
						errors = true;
					} catch (TransformationEngineException e) {
						caughtException = e;
						e.printStackTrace();
						errors = true;
					} catch (NoSuchEngineException e) {
						// Should never occur!!
						caughtException = e;
						e.printStackTrace();
						errors = true;
					}
					finally {
						monitor.done();
					}
					
				}
			});
		} catch (InvocationTargetException e1) {
			// Not relevant
			e1.printStackTrace();
			return false;
		} catch (InterruptedException e1) {
			// Not relevant
			e1.printStackTrace();
			return false;
		}
		
		if (errors) {
			MessageDialog.openError(new Shell(), "Exception occurred!", "The transformation couldn't be completed because an exception was raised:\n" + caughtException.getMessage().replaceAll("\r", ""));
			return false;
		}
		if (!results.isOK()) {
			MessageDialog.openError(new Shell(), "Transformation returned error!", "The transformation engine returned an error:\n" + results.getMessage());
			return false;
		}
		MessageDialog.openInformation(new Shell(), "Transformation completed!!", "The transformation was completed. Results were stored in:\n" + outputPath.getAbsolutePath());
		return true;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#needsPreviousAndNextButtons()
	 */
	@Override
	public boolean needsPreviousAndNextButtons() {
		return true;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#getNextPage(org.eclipse.jface.wizard.IWizardPage)
	 */
	@Override
	public IWizardPage getNextPage(IWizardPage page) {
		if (page instanceof SelectTransformationAndPathPage) {
			if (pages[1] == null || inputPageUsed) {
				addPage(new SelectTransformationInputsPage());
				inputPageUsed = false;
			}
			return pages[1];
		}
		else {
			inputPageUsed = true;
			return null;
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#getPreviousPage(org.eclipse.jface.wizard.IWizardPage)
	 */
	@Override
	public IWizardPage getPreviousPage(IWizardPage page) {
		if (page instanceof SelectTransformationInputsPage) {
			inputPageUsed = true;
			return pages[0];
		}
		else {
			return null;
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#canFinish()
	 */
	@Override
	public boolean canFinish() {
		return canFinish;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#addPages()
	 */
	@Override
	public void addPages() {
		IWizardPage page = new SelectTransformationAndPathPage();
		addPage(page);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#addPage(org.eclipse.jface.wizard.IWizardPage)
	 */
	@Override
	public void addPage(IWizardPage page) {
		if (page instanceof SelectTransformationAndPathPage) {
			pages[0] = page;
		}
		else {
			pages[1] = page;
		}
		
		if (map.containsKey(page.getName())) {
			map.remove(page.getName());
		}
		map.put(page.getName(), page);
		page.setWizard(this);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#getPage(java.lang.String)
	 */
	@Override
	public IWizardPage getPage(String name) {
		return map.get(name);
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#getPages()
	 */
	@Override
	public IWizardPage[] getPages() {
		int count = 0;
		for (int i = 0; i < pages.length; i++) {
			if (pages[i] != null) {
				count++;
			}
		}
		
		IWizardPage[] result = new IWizardPage[count];
		count = 0;
		for (int i = 0; i < pages.length; i++) {
			if (pages[i] != null) {
				result[count++] = pages[i];
			}
		}
		return result;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#getPageCount()
	 */
	@Override
	public int getPageCount() {
		return 2;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#getStartingPage()
	 */
	@Override
	public IWizardPage getStartingPage() {
		return pages[0];
	}

	/**
	 * Internal class implementing the wizard page allowing the user to define the
	 * transformation that will be executed and the output path for the transformation
	 * results.
	 *
	 * @author Adri�n Noguero (adrian.noguero@esi.es)
	 * @version 1.0
	 * @since 1.0
	 *
	 */
	private class SelectTransformationAndPathPage extends WizardPage {

		private TreeViewer transformationViewer;
		private Text outputText;
		
		// Listener implementations
		private ISelectionChangedListener transformationListener = new ISelectionChangedListener() {
			
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				Object selection = ((StructuredSelection)transformationViewer.getSelection()).getFirstElement();
				if (selection instanceof ITransformation) {
					selectedTransformation = (ITransformation)selection;
				}
				else {
					selectedTransformation = null;
				}
				updateStatus();
			}
		};
		
		private SelectionListener browseListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// Not needed
			}

			@Override
			public void widgetSelected(SelectionEvent e) {
				DirectoryDialog dialog = new DirectoryDialog(new Shell());
				dialog.setFilterPath(ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
				dialog.setText("Select the output path");
				dialog.setMessage("Select the output path for the current transformation");
				String result = dialog.open();
				if (result != null) {
					outputPath = new File(result);
					outputText.setText(result);
				}
				else {
					outputPath = null;
					outputText.setText("");
				}
				updateStatus();
			}
			
		};
		
		/**
		 * Default constructor of the class.
		 */
		protected SelectTransformationAndPathPage() {
			super("Select Transformation and Output Path");
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
		 */
		@Override
		public void createControl(Composite parent) {
			Composite topPanel = new Composite(parent, SWT.NONE);
			topPanel.setLayout(new GridLayout(6, true));
			
			Label analysisLabel = new Label(topPanel, SWT.NONE);
			GridData analysisLabelData = new GridData(GridData.FILL_HORIZONTAL);
			analysisLabelData.horizontalSpan = 6;
			analysisLabel.setText("Select a registered transformation to run:");
			analysisLabel.setLayoutData(analysisLabelData);
			
			transformationViewer = new TreeViewer(topPanel, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
			GridData transformationViewerData = new GridData(GridData.FILL_BOTH);
			transformationViewerData.horizontalSpan = 6;
			transformationViewer.getTree().setLayoutData(transformationViewerData);
			updateTransformationData();
			
			Label outputPathLabel = new Label(topPanel, SWT.NONE);
			GridData outputPathLabelData = new GridData(GridData.FILL_HORIZONTAL);
			outputPathLabelData.horizontalSpan = 6;
			outputPathLabel.setText("Set the output path:");
			outputPathLabel.setLayoutData(outputPathLabelData);
			
			outputText = new Text(topPanel, SWT.LEFT | SWT.SINGLE);
			GridData outputPathTextData = new GridData(GridData.FILL_HORIZONTAL);
			outputPathTextData.horizontalSpan = 5;
			outputText.setLayoutData(outputPathTextData);
			outputText.setEditable(false);
			
			Button browseButton = new Button(topPanel, SWT.PUSH | SWT.CENTER);
			GridData browseButtonData = new GridData();
			browseButtonData.horizontalSpan = 1;
			browseButton.setLayoutData(browseButtonData);
			browseButton.setText("Browse...");
			
			// Add listeners
			transformationViewer.addSelectionChangedListener(transformationListener);
			browseButton.addSelectionListener(browseListener);
			
			// Set the title & description
			setTitle("Select transformation and output container");
			setDescription("Select the transformation to execute and the output path for transformation results container.");
			
			// Set the control!!
			setControl(topPanel);
			setPageComplete(false);
		}

		private void updateTransformationData() {
			Tree data = transformationViewer.getTree();
			data.removeAll();
			for (ITransformation transformation : ModelTransformatorPlugin.getService().getTransformationRepository()) {
				TreeItem item = new TreeItem(data, SWT.NONE);
				Image icon = ModelTransformatorPlugin.getService().getEngineImage(transformation.getEngine());
				if (transformation.getResourceType().equals(ResourceType.PROTECTED)) {
					icon = new OverlaidImageDescriptor(icon, CorePlugin.getImage(CorePlugin.LOCK_OVERLAY), CorePlugin.BOTTOM_RIGHT).getImage();
				}
				else if (transformation.getResourceType().equals(ResourceType.NETWORKED)) {
					icon = new OverlaidImageDescriptor(icon, CorePlugin.getImage(CorePlugin.NET_OVERLAY), CorePlugin.BOTTOM_RIGHT).getImage();
				}
				item.setText(transformation.getName());
				item.setImage(icon);
				item.setData(transformation);
			}
			transformationViewer.setSelection(TreeSelection.EMPTY);
		}
		
		private void updateStatus() {
			setPageComplete(outputPath != null && outputPath.isDirectory() && outputPath.canWrite() && selectedTransformation != null);
			canFinish = false;
		}
		
	}
	
	/**
	 * Internal class implementing the wizard page allowing the user to define the
	 * inputs the current transformation will use.
	 *
	 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
	 * @version 1.0
	 * @since 1.0
	 *
	 */
	private class SelectTransformationInputsPage extends WizardPage {

		private List<EClass> inputTypes;
		private HashMap<TreeViewer, Integer> widget2TypeMap = new HashMap<TreeViewer, Integer>();
		private HashMap<Integer, EObject> type2InputMap = new HashMap<Integer, EObject>();
		
		/**
		 * Special Selection Listener implementation used for the TreeViewer in this page.
		 *
		 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
		 * @version 1.0
		 * @since 1.0
		 *
		 */
		private class MyBrowseListener implements SelectionListener {
			
			private TreeViewer viewer;
			private Text path;
			
			/**
			 * Constructor of the listener with a Text and a TreeViewer elements.
			 *
			 *@param modelPath the Text widget containing the model path.
			 *@param modelViewer the TreeViewer containing the model.
			 */
			public MyBrowseListener(Text modelPath, TreeViewer modelViewer) {
				path = modelPath;
				viewer = modelViewer;
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// Not needed
			}

			@Override
			public void widgetSelected(SelectionEvent e) {
				FileDialog dialog = new FileDialog(new Shell());
				dialog.setText("Select a model file");
				String strPath = dialog.open();
				if (strPath != null) {
					// Load the model from the file
					ResourceSet rs = new ResourceSetImpl();
					URI modelURI = URI.createFileURI(strPath);
					Resource resource = null;
					try {
						resource = rs.getResource(modelURI, true);
						path.setText(strPath);
						viewer.setInput(null);
						viewer.setInput(resource);
					}
					catch (Exception ex) {
						MessageDialog.openError(getShell(), "Not valid model file", "The provided model file couldn't be parsed as an EMF resource");
						path.setText("");
						viewer.setInput(null);
					}
				}
			}
		}
		
		/**
		 * Default constructor of the class.
		 */
		protected SelectTransformationInputsPage() {
			super("Select Transformation Inputs");
			
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
		 */
		@Override
		public void createControl(Composite parent) {
			Composite topPanel = new Composite(parent, SWT.NONE);
			topPanel.setLayout(new GridLayout(6, true));
			
			inputTypes = selectedTransformation.getRequiredInputList();
			int inputNo = selectedTransformation.getRequiredInputList().size();
			
			for (int i = 0; i < inputNo; i++) {
				Label selectModel = new Label(topPanel, SWT.NONE);
				GridData selectLabelData = new GridData(GridData.FILL_HORIZONTAL);
				selectLabelData.horizontalSpan = 6;
				selectModel.setText("Select Input #" + (i+1) + " of type " + inputTypes.get(i).getName() +":");
				selectModel.setLayoutData(selectLabelData);
				
				Text modelPath = new Text(topPanel, SWT.BORDER | SWT.LEFT | SWT.SINGLE);
				GridData pathTextData = new GridData(GridData.FILL_HORIZONTAL);
				pathTextData.horizontalSpan = 5;
				modelPath.setLayoutData(pathTextData);
				modelPath.setEditable(false);
				
				Button browse = new Button(topPanel, SWT.PUSH | SWT.CENTER);
				GridData butData = new GridData(GridData.FILL_HORIZONTAL);
				butData.horizontalSpan = 1;
				browse.setLayoutData(butData);
				browse.setText("Browse");
				
				TreeViewer modelViewer = new TreeViewer(topPanel, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
				GridData modelViewerData = new GridData(GridData.FILL_BOTH);
				modelViewerData.horizontalSpan = 6;
				modelViewer.getTree().setLayoutData(modelViewerData);
				widget2TypeMap.put(modelViewer, i);
				
				ISelectionChangedListener selectObjectListener = new ISelectionChangedListener() {

					@Override
					public void selectionChanged(SelectionChangedEvent e) {
						Object selectedElement = ((StructuredSelection)e.getSelection()).getFirstElement();
						if (selectedElement instanceof EObject && e.getSource() instanceof TreeViewer) {
							Integer order = widget2TypeMap.get(e.getSource());
							type2InputMap.remove(order);
							type2InputMap.put(order, (EObject) selectedElement);
						}
						updateStatus();
					}

				};

				ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
				modelViewer.setContentProvider(new AdapterFactoryContentProvider(adapterFactory));
				modelViewer.setLabelProvider(new AdapterFactoryLabelProvider(adapterFactory));
				modelViewer.setSorter(new ViewerSorter());
				
				// Add listeners
				MyBrowseListener buttonListener = new MyBrowseListener(modelPath, modelViewer);
				browse.addSelectionListener(buttonListener);
				modelViewer.addSelectionChangedListener(selectObjectListener);
				
			}
			
			// Set the description
			setTitle("Select the inputs");
			setDescription("Select the input models for the transformation.");
			
			// Set the control!!
			setControl(topPanel);
			setPageComplete(false);
			
			inputPageUsed = true;
		}
		
		
		private void updateStatus() {
			// Check that all the required inputs have been provided
			inputs.clear();
			for (int i = 0; i < inputTypes.size(); i++) {
				EObject input = type2InputMap.get(i);
				EClass type = inputTypes.get(i);
				if (input == null || !type.isInstance(input)) {
					canFinish = false;
					setPageComplete(false);
					return;
				}
				else {
					inputs.add(input);
				}
			}
			canFinish = true;
			setPageComplete(false);
		}
		
	}

}
